use proc_macro::TokenStream;
use quote::{quote, format_ident, ToTokens};
use syn::{parse_macro_input, DeriveInput};

fn add_member_variables(derive_input: &DeriveInput) -> proc_macro2::TokenStream {
    let mut stream = quote!();

    if let syn::Data::Struct(data_struct) = &derive_input.data {
        for field in &data_struct.fields {
            // Get Value Define
            let field_name = field.ident.as_ref().unwrap();
            let field_name_string = field_name.to_string();
            
            let field_type_name = field.ty.to_token_stream();
            let field_type_name_string = field.ty.to_token_stream().to_string().replace(" ", "");
            
            // Function Name Define
            let get = format_ident!("get_{}_macro", field_name_string);
            let set = format_ident!("set_{}_macro", field_name_string);

            let get_field_name = format_ident!("get_field_name_{}_macro", field_name_string);
            let get_field_type_name = format_ident!("get_field_type_name_{}_macro", field_name_string);

            // Construct
            stream = quote! {
                #stream

                pub fn #get_field_name(&self) -> &'static str {
                    #field_name_string
                }

                pub fn #get_field_type_name(&self) -> &'static str {
                    #field_type_name_string
                }

                pub fn #set(&mut self, input: #field_type_name) {
                    self.#field_name = input;
                } 

                pub fn #get(&self) -> &#field_type_name {
                    &self.#field_name
                } 
            };
        }
    };
    stream
}

#[proc_macro_attribute]
pub fn proc_macro_reflection(_: TokenStream, input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);

    // println!("{:#?}", derive_input);

    // Get Name
    let struct_name = &derive_input.ident;
    let struct_name_string = struct_name.to_string();

    // Add reflection for Member Variables
    let member_variables_quote = add_member_variables(&derive_input);
    // println!("{:#?}", &member_variables_quote.to_string());

    // Code Gen
    let expand = quote! {
        #derive_input
        // Add Code
        impl GeneratedMacro for #struct_name {
            fn generate_macro_print(&self) {
                println!("Struct Name: {}", #struct_name_string);
            }
        }
        // Add 
        impl #struct_name {
            pub fn get_struct_name_macro(&self) -> &'static str {
                #struct_name_string
            }

            #member_variables_quote
        }
    };

    // println!("{:#?}", expand.to_string());

    // Turn into output
    TokenStream::from(expand)
}
